#include <retro.h>
#include "retro.creator.h"

using namespace Retro::Creator::GUI;
using namespace SPLib::Geom;

#define PADDING             1
#define BUFFER_INCREMENT    50
#define DRAG_THRESHOLD      10
#define CLOSE_SIZE          15

Container::Container()
{
  mState      = StateNormal;
  mCollapsed  = FALSE;
  mResize     = FALSE;
  mCloseable  = FALSE;
  mDraggable  = FALSE;
  mActive     = FALSE;
  mOldHeight  = 200;
  mBuffer     = NULL;
  mContent    = NULL;
}

VOID    Container::getCloseBounds(LONG cx, Geom::Bounds &bounds)
{
  bounds = Geom::Bounds::get(
    cx - CLOSE_SIZE - 3,
    (TITLEBOX_HEIGHT - CLOSE_SIZE) / 2,
    CLOSE_SIZE, CLOSE_SIZE);
}

VOID    Container::onCreate(Event::WndCreateEvent& iEvent)
{
  mLayout = getHandle();
  mLayout.style = LayoutFixed;
}

VOID    Container::onDestroy()
{
  mLayout.clear();
  delete mBuffer;
}

VOID    Container::onSize(Event::SizeEvent& iEvent)
{
  LayoutElement* pRoot = Frame::get()->getRoot();

  if (!pRoot) return;

  LayoutElement* pElem = pRoot->findElement(*this);

  if (pElem)
  {
    if (pElem->dock != DockTop && pElem->dock != DockBottom && mCollapsed)
      setCollapsed(FALSE);

    mCollapsed = iEvent.cy == TITLEBOX_HEIGHT;

    if (iEvent.cy < TITLEBOX_HEIGHT)
    {
      iEvent.cy = TITLEBOX_HEIGHT;
      Frame::get()->doLayout();
    }

    invalidate(Geom::Rect::get(0, 0, iEvent.cx, TITLEBOX_HEIGHT), FALSE);
    doLayout();
  }

  // update buffer
  if (mBuffer)
  {
    INT cxBuffer = mBuffer->GetWidth();

    if (cxBuffer < iEvent.cx)
    {
      cxBuffer = iEvent.cx + BUFFER_INCREMENT;
    }
    else if (cxBuffer - BUFFER_INCREMENT * 2 > iEvent.cx)
    {
      cxBuffer = iEvent.cx + BUFFER_INCREMENT;
    }

    if (mBuffer->GetWidth() != cxBuffer)
    {
      delete mBuffer;
      mBuffer = new Gdip::Bitmap(cxBuffer, TITLEBOX_HEIGHT);
    }
  }
  else
  {
    mBuffer = new Gdip::Bitmap(
      iEvent.cx + BUFFER_INCREMENT, TITLEBOX_HEIGHT);
  }
}

VOID    Container::onPaint()
{
  PAINTSTRUCT paint;
  BeginPaint(*this, &paint);

  Rect wndRect = Rect::fromClient(*this);
  {
    Gdip::Graphics g(paint.hdc);
    Gdip::Graphics bg(mBuffer);

    Gdip::Rect invalid(
      paint.rcPaint.left,
      paint.rcPaint.top,
      paint.rcPaint.right - paint.rcPaint.left,
      paint.rcPaint.bottom - paint.rcPaint.top);
    Gdip::RectF bounds(
      0,
      0,
      wndRect.right - 1.0f,
      wndRect.bottom - 1.0f);
    Gdip::RectF box(
      0.0f, 0.0f,
      wndRect.right - 1.0f,
      TITLEBOX_HEIGHT);

    PTCHAR szTitle = getText();

    bg.SetSmoothingMode(Gdip::SmoothingModeAntiAlias);
    bg.SetPixelOffsetMode(Gdip::PixelOffsetModeHalf);
    bg.FillRectangle(
      &Gdip::SolidBrush(Control::getTheme().frameBg),
      invalid);

    {
      Gdip::GraphicsPath header;

      if (mCollapsed)
        getRoundedRect(header, box, 6.0f);
      else
        getHalfRoundedRect(header, box, 6.0f);

      bg.FillPath(
        &Gdip::LinearGradientBrush(box,
          mActive ?
            0xFFCCCCCC :
            Control::getTheme().frameSurface,
          mActive ?
            0xFFFFFFFF :
            Control::getTheme().frameDark,
          Gdip::LinearGradientModeVertical), &header);
    }

    {
      Gdip::StringFormat format;

      format.SetAlignment(Gdip::StringAlignmentCenter);
      format.SetLineAlignment(Gdip::StringAlignmentCenter);

      bg.DrawString(szTitle, -1,
        &Gdip::Font(_T("Arial"), 11, Gdip::FontStyleBold),
        Gdip::RectF(box.X, box.Y + 1, box.Width, box.Height),
        &format,
        &Gdip::SolidBrush(mActive ?
          Control::getTheme().textDark :
          Control::getTheme().textLight));
    }

    {
      Gdip::GraphicsPath border;

      if (mCollapsed)
        getRoundedRect(border, bounds, 6.0f);
      else
        getHalfRoundedRect(border, bounds, 6.0f);

      bg.SetPixelOffsetMode(Gdip::PixelOffsetModeNone);

      bg.ExcludeClip(box);
      bg.FillPath(
        &Gdip::SolidBrush(Control::getTheme().containerBg),
        &border);

      bg.SetClip(invalid);
      bg.DrawPath(
        &Gdip::Pen(Control::getTheme().containerBorder),
        &border);
    }

    if ((mState == StateOver ||
        mState == StateCloseOver ||
        mState == StateCloseDown) &&
        mCloseable)
    {
      COLORREF lineColor = mActive ?
        Control::getTheme().textLight :
        Control::getTheme().frameDark;

      Bounds close;
      getCloseBounds(wndRect.getWidth(), close);

      bg.FillEllipse(
        &Gdip::SolidBrush(mActive ?
          Control::getTheme().textDark :
          Control::getTheme().textLight),
        close.x, close.y, close.cx, close.cy);

      if (mState == StateCloseDown)
        close.inflate(-5, -5);
      else
        close.inflate(-4, -4);

      bg.DrawLine(&Gdip::Pen(lineColor, 2.0f),
        close.x, close.y, close.x + close.cx, close.y + close.cy);
      bg.DrawLine(&Gdip::Pen(lineColor, 2.0f),
        close.x + close.cx, close.y, close.x, close.y + close.cy);
    }

    g.SetClip(invalid);
    g.DrawCachedBitmap(&Gdip::CachedBitmap(mBuffer, &g), 0, 0);
    g.ExcludeClip(Gdip::Rect(0, 0, wndRect.right, TITLEBOX_HEIGHT));

    g.FillRectangle(
      &Gdip::SolidBrush(Control::getTheme().containerBg),
      invalid);
    g.DrawRectangle(
      &Gdip::Pen(Control::getTheme().containerBorder),
      bounds);

    delete[] szTitle;
  }

  EndPaint(*this, &paint);
}

VOID    Container::onEraseBackground(Event::EraseBackgroundEvent& iEvent)
{
  *iEvent.handled = TRUE;
}

VOID    Container::setCollapsed(BOOL value)
{
  LayoutElement* pElem = Frame::get()->getContent()->findElement(*this);

  if (mCollapsed != value && pElem)
  {
    if (value)
    {
      if (pElem->dock == DockTop || pElem->dock == DockBottom)
      {
        mOldHeight = pElem->size.cy;

        pElem->size.cy = TITLEBOX_HEIGHT;
        pElem->style = LayoutFixed;

        if (Frame::get()->getActiveCont() == this)
        {
          Frame::get()->setActiveCont(NULL);
          SetFocus(NULL);
        }
      }
      else
      {
        return;
      }
    }
    else
    {
      pElem->size.cy = mOldHeight;
      pElem->style = LayoutFixed;
    }

    mCollapsed = value;
    Frame::get()->doLayout();
  }
}

VOID    Container::setActive(BOOL value)
{
  if (mActive != value)
  {
    Geom::Rect rect = Geom::Rect::fromClient(*this);

    if (mActive = value)
      SetFocus(mContent ? mContent : *this);

    rect.bottom = TITLEBOX_HEIGHT;
    invalidate(rect);
  }
}

VOID    Container::onMouseDown(Event::MouseEvent& iEvent)
{
  SetForegroundWindow(mContent ? mContent : *this);

  if (mState != StateNormal)
  {
    if (iEvent.mkButton & MK_LBUTTON && mState != StateCloseOver)
    {
      mState = StateGrab;
      mPoint = iEvent.point;
    }

    if (mCloseable)
    {
      invalidateClose();

      if (mState == StateCloseOver)
        mState = StateCloseDown;
    }
  }
}

VOID    Container::onMouseUp(Event::MouseEvent& iEvent)
{
  LayoutDock dock;

  switch (mState)
  {
  case StateCloseDown:
    delete Frame::get()->getContent()->removeElement(*this);
    Frame::get()->getContent()->clean();
    Frame::get()->doLayout();
    break;
  case StateDrag:
    dock = Frame::get()->getRoot()->endDrag();

    if (dock != DockNone)
    {
      if (dock != DockTop && dock != DockBottom && mCollapsed)
        setCollapsed(FALSE);
      else
        Frame::get()->doLayout();
    }
      break;
  case StateGrab:
    setCollapsed(!mCollapsed);
    break;
  }

  invalidateClose();
  mState = StateNormal;
}

VOID    Container::onMouseMove(Event::MouseEvent& iEvent)
{
  BOOL isOver = iEvent.y < TITLEBOX_HEIGHT;

  if (mState == StateDrag)
  {
    Frame::get()->getRoot()->drag();
  }
  else if (mState == StateGrab && mDraggable)
  {
    if (Vector::Vector2D((REAL)iEvent.x, (REAL)iEvent.y).distanceSq(
      Vector::Vector2D((REAL)mPoint.x, (REAL)mPoint.y)) >
      DRAG_THRESHOLD * DRAG_THRESHOLD)
    {
      mState = StateDrag;
      Frame::get()->getRoot()->beginDrag(*this);
    }
  }
  else if (mCloseable)
  {
    BOOL wasOver = mState == StateCloseOver || mState == StateCloseDown;
    BOOL isOverClose;
    Bounds closeBounds;
    Rect closeRect = getClient();

    getCloseBounds(closeRect.getWidth(), closeBounds);
    isOverClose = closeBounds.contains(iEvent.point);

    if (wasOver != isOverClose || isOver != (mState != StateNormal))
    {
      mState = isOverClose ? StateCloseOver :
        (isOver ? StateOver : StateNormal);

      closeBounds.inflate(2, 2);
      closeBounds.toRect(closeRect);
      invalidate(closeRect);
    }
  }
  else if (isOver)
  {
    mState = StateOver;
  }
}

VOID    Container::onMouseLeave()
{
  if (mState == StateCloseOver || mState == StateOver)
    invalidateClose();

  mState = StateNormal;
}

VOID    Container::invalidateClose()
{
  Bounds closeBounds;
  Rect closeRect = getClient();

  getCloseBounds(closeRect.getWidth(), closeBounds);

  closeBounds.inflate(2, 2);
  closeBounds.toRect(closeRect);
  invalidate(closeRect);
}

VOID    Container::doLayout()
{
  Rect client = getClient();

  client.left += PADDING;
  client.top += TITLEBOX_HEIGHT;
  client.right -= PADDING;
  client.bottom -= PADDING;

  mLayout.doLayout(client);
}
